;=======
;常量
;=======
app_lba_start equ 99
FDC_DOR         equ 0x3F2  ; 数字输出寄存器
FDC_MSR         equ 0x3F4  ; 主状态寄存器
FDC_DATA        equ 0x3F5  ; 数据寄存器
FLPY_SECTORS_PER_TRACK equ 18

SECTION mbr align=16 vstart=0x7c00
    ;设置段地址
    mov ax,0
    mov ss,ax
    mov sp,ax

    mov ax,[cs:phy_base] ; 程序加载处的物理地址
    mov dx,[cs:phy_base+0x02] ; 4字节
    mov bx,16  
    div bx         ; 取段地址
    mov ds,ax
    mov es,ax

    ;先读取程序头部
    xor di,di
    mov si,app_lba_start
    xor bx,bx
    call read_floppy_disk_0

    ;获取程序总长度，并继续读取，直到所有扇区全部load进内存
    mov dx,[0x02]
    mov ax,[0x00]
    mov bx,512
    div bx
    cmp dx,0
    jnz @1 ; 余数不为 0，要读取 ax+1 个扇区。由于已经读了一个扇区了，所以还要读 ax 个。
    dec ax ; 余数为 0，证明刚好 ax 个扇区，不用读取第 ax+1 个扇区了。由于已经读了一个扇区了，所以还要读 ax-1 个。
@1:
    cmp ax,0
    jz direct ; 商为 0，证明总长度不足 512 字节，刚才已经读了一个字节出来了，已经是所有内容了，所以不用再读了

    push ds   ; 会改变 ds 的值，但后面还要以初始的 ds 值为准去读内存，所以先压栈
    mov cx,ax
read_continue:
    inc si
    xor di,di
    mov ax,ds
    add ax,0x20 ; 512个字节就是512个内存单元，就是0x200，所以段地址一次加0x20
    mov ds,ax
    xor bx,bx
    call read_floppy_disk_0
    loop read_continue

    pop ds

; 全部读入内存后，进行用户程序的重定位
direct:
    mov dx,[0x08]
    mov ax,[0x06]
    call calc_segment_base
    mov [0x06],ax
    ; 处理程序段
    mov cx,[0x0a]
    mov bx,0x0c  ;表首地址（第一条）
realloc:
    mov ax,[bx]
    mov dx,[bx+2]
    call calc_segment_base
    mov [bx],ax
    add bx,0x04
    loop realloc

    jmp far [0x04]

;================
; 读1个扇区的软盘数据到内存
; di、si - 扇区 LBA 值，使用di的低12位和si的全部16位，共计28位
; ds:bx - 写入内存逻辑地址
; TODO dx - 读取的扇区数（待补充，暂时没有这个参数，只能读取一个扇区）
;================
read_floppy_disk_0:
    push ax
    push bx
    push cx
    push dx
    push es

    mov ax,ds
    mov es,ax

    ; 再计算柱面号 
    mov cx,FLPY_SECTORS_PER_TRACK*2
    mov ax,si
    mov dx,di
    div cx
    mov [cs:@3],al

    ; 再计算磁头号
    mov cx,FLPY_SECTORS_PER_TRACK*2
    mov ax,si
    mov dx,di
    div cx
    mov ax,dx
    xor dx,dx
    mov cx,FLPY_SECTORS_PER_TRACK
    div cx
    mov [cs:@3+1],al

    ; 计算扇区号
    mov cx,18
    mov ax,si
    mov dx,di
    div cx
    inc dx
    mov [cs:@3+2],dl

    ; 设置 LBA 100 扇区
    mov ah, 0x02        ; BIOS中断 13h，读取扇区
    mov al, 0x01        ; 读取1个扇区
    mov ch, [cs:@3]        ; 柱面号 0
    mov cl, [cs:@3+2]        ; 扇区号 100
    mov dh, [cs:@3+1]        ; 磁头号 0
    mov dl, 0x00        ; 驱动器号 0 (软盘)

    ; 读取软盘的 LBA 100 扇区到内存地址 0x10000
    mov bx, 0x0000      ; BX指向0x0000 (0x1000:0x0000 = 0x10000)
    int 0x13            ; 调用BIOS中断读取扇区

    ; 检查读取是否成功
    jc disk_error       ; 如果载入失败，跳转到错误处理

    pop es
    pop dx
    pop cx
    pop bx
    pop ax

    ret

;====================
; 重定位用户程序
; dx:ax - 用户程序原本的段起始地址
;====================
calc_segment_base:
    push dx
    add ax,[cs:phy_base]
    adc dx,[cs:phy_base+0x02]
    shr ax,4  ;低16位右移4位，空出最左边4位
    ror dx,4  ;高16位循环右移4位，将最低4位的有效值挪到最高4位上
    and dx,0xf000 ;确保只保留最高四位
    or ax,dx  ; 合并
    pop dx
    ret

disk_error:
    ; 错误提示，然后进入低功耗模式
    mov ax,0xb800
    mov es,ax
    mov byte [es:0x00],"E"
    mov byte [es:0x01],0x07
    mov byte [es:0x02],"r"
    mov byte [es:0x03],0x07
    mov byte [es:0x04],"r"
    mov byte [es:0x05],0x07
    mov byte [es:0x06],"o"
    mov byte [es:0x07],0x07
    mov byte [es:0x08],"r"
    mov byte [es:0x09],0x07
    hlt                ; 进入停机状态

@3: db 0,0,0    ; 用于存储 柱面、磁头、扇区

phy_base: dd 0x10000   ;程序被加载处的物理地址

times 510-($-$$) db 0
db 0x55, 0xaa